package chatserver.objects;

import chatserver.sql.MySqlConnection;
import chatserver.container.AutoOpInfo;
import chatserver.container.BanInfo;
import chatserver.tools.IpSubnetFilter;
import chatserver.netio.AttrList;
import chatserver.netio.Perms;
import chatserver.roles.ChannelRole;
import chatserver.roles.Rights;
import chatserver.logging.ServerLog;
import chatserver.netio.Command;
import chatserver.netio.Connection;
import chatserver.netio.ClientAttr;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;

public class Channel {

    // session variables
    private Map<User, ChannelRole> connectedUserRoles;
    private Map<User, ChannelRole> connectedUserAutoRoles;
    private Map<User, ChannelRole> connectedUserDecidingRoles;
    private List<User> forceChannelRoleProfileUsers;
    private Server server;
    private int sqlId;
    private MySqlConnection mySql;
    private boolean createdByGuest;
    private boolean isClosed;
    private TreeMap<Long, byte[]> eventLog;
    // saved variables
    private String nameId;
    private String name;
    private Profile profile;
    private String topic;
    private String topicBy;
    private String createdBy;
    private String bgImageChat;
    private String bgImageList;
    private int slots;
    private boolean isStatic;
    private boolean isImportant;
    private List<BanInfo> banned;

    public enum Profile {

        DEFAULT("normal"), MODERATED("moderated"), VIP("vip"), ADMIN("admin");
        private String profile;

        private Profile(String profile) {
            this.profile = profile;
        }

        public String getProfile() {
            return this.profile;
        }
    }

    public Channel(String nameId, String name, boolean isStatic, boolean isImportant, Profile profile, int slots, String topic, String topicBy, String createdBy) {

        initChannel(nameId, name, isStatic, isImportant, profile, slots, topic, topicBy, createdBy);

        this.createdByGuest = false;

        loadFromSql();
    }

    public Channel(String nameId, String name, boolean isStatic, boolean isImportant, Profile profile, int slots, String topic, String topicBy, User invoker) {

        initChannel(nameId, name, isStatic, isImportant, profile, slots, topic, topicBy, invoker.getName());

        this.createdByGuest = invoker.isGuest();

        loadFromSql();
    }

    private void initChannel(String nameId, String name, boolean isStatic, boolean isImportant, Profile profile, int slots, String topic, String topicBy, String createdBy) {
        this.eventLog = new TreeMap<Long, byte[]>();

        this.isClosed = false;
        this.bgImageChat = "";
        this.bgImageList = "";
        this.server = Server.getInstance();
        this.connectedUserRoles = Collections.synchronizedMap(new HashMap<User, ChannelRole>());
        this.connectedUserAutoRoles = Collections.synchronizedMap(new HashMap<User, ChannelRole>());
        this.connectedUserDecidingRoles = Collections.synchronizedMap(new HashMap<User, ChannelRole>());
        this.forceChannelRoleProfileUsers = Collections.synchronizedList(new ArrayList<User>());
        this.nameId = nameId;
        this.name = name;
        this.isStatic = isStatic;
        this.isImportant = isImportant;
        this.profile = profile;
        this.topic = topic;
        this.slots = slots;
        this.topicBy = topicBy;
        this.createdBy = createdBy;
        this.sqlId = -1;
        this.mySql = MySqlConnection.getInstance();

    }

    private void loadFromSql() {
        Map<String, String> channelData = mySql.loadChannelData(nameId, name, isStatic, isImportant, profile, slots, topic, topicBy, createdBy, createdByGuest);

        try {
            this.nameId = channelData.get("nameid");
            this.name = channelData.get("name");
            this.isStatic = channelData.get("isstatic").equals("1") ? true : false;
            this.isImportant = channelData.get("isimportant").equals("1") ? true : false;
            this.profile = Profile.valueOf(channelData.get("profile"));
            this.slots = Integer.valueOf(channelData.get("slots"));
            this.topic = channelData.get("topic");
            this.topicBy = channelData.get("topicby");
            this.createdBy = channelData.get("createdby");
            this.bgImageChat = channelData.get("bgimagechat");
            this.bgImageList = channelData.get("bgimagelist");
            this.sqlId = Integer.parseInt(channelData.get("id"));
            this.banned = mySql.getChannelBans(sqlId);
        } catch (Exception e) {
            ServerLog.logError("while loading channel '" + name + "': " + e.getMessage());
        }
    }

    private void saveToSql() {
        if (sqlId == -1) {
            return;
        }
        mySql.updateChannelData(sqlId, nameId, name, isStatic, isImportant, profile, slots, topic, topicBy, createdBy, bgImageChat, bgImageList);
    }

    public void rehash() {
        if (sqlId == -1) {
            return;
        }
        loadFromSql();
    }

    public int getSqlId() {
        return sqlId;
    }

    public boolean userHasChannelRight(User u, ChannelRole r, Rights.Channel right) {
        if (u.hasGlobalRight(Rights.Global.AdministrateChannels)) {
            return true;
        }

        return r.hasRight(right);
    }

    public boolean userHasChannelRightWithAnyModifier(User u, ChannelRole r, Rights.Channel right) {
        if (u.hasGlobalRight(Rights.Global.AdministrateChannels)) {
            return true;
        }

        return r.hasRightWithAnyModifier(right);
    }

    public boolean userHasChannelRight(User u, ChannelRole r, User target, ChannelRole rTarget, Rights.Channel right) {
        if (u.hasGlobalRight(Rights.Global.AdministrateChannels)) {
            if (!target.hasGlobalRight(Rights.Global.AdministrateChannels)) {
                return true;
            } else if (target != null) {
                if (u.hasGlobalRight(Rights.Global.ServerOp, target.getDecidingGlobalRoleSqlId())) {
                    return true;
                } else {
                    return false;
                }
            }
        }

        if (target.hasGlobalRight(Rights.Global.AdministrateChannels)) {
            if (!u.hasGlobalRight(Rights.Global.AdministrateChannels)) {
                return false;
            } else {
                if (target.hasGlobalRight(Rights.Global.ServerOp, u.getDecidingGlobalRoleSqlId())) {
                    return true;
                } else {
                    return false;
                }
            }
        }

        return r.hasRight(right, rTarget.getSqlId());
    }

    private void addToEventLog(byte[] event) {
        Date now = new Date();
        long time = (int) (now.getTime() / 1000);
        eventLog.put(time, event);

        if (eventLog.size() > 500) {
            for (int i = 0; i < eventLog.size() - 400; i++) {
                eventLog.remove(eventLog.firstEntry().getKey());
            }
        }
    }

    public void getEventLog(User invoker, long startTime) {
        if (!connectedUserRoles.containsKey(invoker)) {
            return;
        }

        if (!invoker.hasGlobalRight(Rights.Global.ViewChannelLog)) {
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            Command cmd = new Command(Command.NOPERMISSION, at);
            invoker.getConnection().sendCommand(cmd);
            return;
        }

        AttrList at = new AttrList();

        SortedMap<Long, byte[]> events = eventLog.tailMap(startTime);
        Iterator<Entry<Long, byte[]>> iter = events.entrySet().iterator();

        while (iter.hasNext()) {
            Entry<Long, byte[]> event = iter.next();

            // at.safeAddClientAttr(ClientAttr.LOG_TIME, event.getKey().intValue());
            at.safeAddClientAttr(ClientAttr.LOG_EVENT, event.getValue());
        }

        Command cmd = new Command(Command.LOG, at);
        invoker.getConnection().sendCommand(cmd);

    }

    private void addBan(Connection con, String name, int ipSize, long expires, String banner) {
        name = name.toLowerCase();
        BanInfo newBan = new BanInfo(name, con.getClientIp(), ipSize, expires, banner);
        banned.add(newBan);

        if (sqlId != -1) {
            mySql.addChannelBan(sqlId, newBan);
        }
    }

    private boolean delBanByName(String name) {
        boolean ret = false;
        name = name.toLowerCase();
        Iterator<BanInfo> iter = banned.iterator();
        while (iter.hasNext()) {
            BanInfo info = iter.next();
            if (info.name.equals(name)) {
                iter.remove();
                ret = true;
            }
        }

        if (sqlId != -1) {
            mySql.delChannelBanByName(sqlId, name);
        }

        return ret;
    }

    public boolean isBanned(User u) {

        String userName = u.getLowerCaseName();

        if (banned == null)
            this.banned = mySql.getChannelBans(sqlId);
        
        Iterator<BanInfo> iter = banned.iterator();

        while (iter.hasNext()) {

            BanInfo bi = iter.next();

            if (bi.isExpired()) {
                iter.remove();
                if (sqlId != -1) {
                    mySql.delServerBan(bi);
                }
                continue;
            }

            if (bi.name.toLowerCase().equals(userName)) {
                return true;
            } else if (IpSubnetFilter.ipMatches(u.getConnection().getClientIp(), bi.ip, bi.ipSize)) {
                return true;
            }
        }

        return false;
    }

    public void getBanList(User invoker) {

        ServerLog.log("ADZ - Channel.java->getBanList:User="+invoker);
        ChannelRole crole = getDecidingChannelRoleOfConnectedUser(invoker);

        if (!userHasChannelRight(invoker, crole, Rights.Channel.ViewBanList)) {
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            Command cmd = new Command(Command.NOPERMISSION, at);
            invoker.getConnection().sendCommand(cmd);
            return;
        }

        AttrList at = new AttrList();

        at.safeAddClientAttr(ClientAttr.CHANNELNAME, name);
        //at.safeAddClientAttr(ClientAttr.CREATORNAME, getCreatedBy());

        ServerLog.log("ADZ - Channel.java->getBanList:banned="+banned+":mySql="+mySql+":sqlId="+sqlId);
        if (banned == null)
            banned = mySql.getChannelBans(sqlId);
        
        Iterator<BanInfo> iter = banned.iterator();
        while (iter.hasNext()) {
            BanInfo bi = iter.next();

            at.safeAddClientAttr(ClientAttr.NICKNAME, bi.name);

            if (invoker.hasGlobalRight(Rights.Global.ViewIP)) {
                at.safeAddClientAttr(ClientAttr.BAN_IPNET, bi.getRawIp());
                if (bi.ipSize != 32) {
                    at.safeAddClientAttr(ClientAttr.BAN_IPSIZE, bi.ipSize);
                }
            }

            at.safeAddClientAttr(ClientAttr.BAN_TIMELEFT, bi.getTimeLeft());
            at.safeAddClientAttr(ClientAttr.BAN_BANNER, bi.banner);
            //at.safeAddClientAttr(ClientAttr.IDENT, ""); 
        }

        Command cmd = new Command(Command.BANLIST, at);
        invoker.getConnection().sendCommand(cmd);
    }

    public void getAutoOpList(User invoker) {
        ServerLog.log("ADZ - Channel.java->getAutoOpList");

        ChannelRole crole = getDecidingChannelRoleOfConnectedUser(invoker);

        if (!userHasChannelRight(invoker, crole, Rights.Channel.ViewBanList)) {
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            Command cmd = new Command(Command.NOPERMISSION, at);
            invoker.getConnection().sendCommand(cmd);
            return;
        }

        List<AutoOpInfo> autooplist = mySql.getChannelAutoOpList(sqlId);

        for (int i = 0; i < autooplist.size(); i++)
        {
            ServerLog.log("ADZ - autooilist_" + i + "=" + autooplist.get(i));
        }
        
        AttrList at = new AttrList();

        at.safeAddClientAttr(ClientAttr.CHANNELNAME, name);
        //at.safeAddClientAttr(ClientAttr.CREATORNAME, getCreatedBy());

        ServerLog.log("ADZ - autooplist=" + autooplist);
        if (autooplist != null && autooplist.size() != 0)
        {
            ServerLog.log("ADZ - autooplist is not empty");
            Iterator<AutoOpInfo> iter = autooplist.iterator();
            while (iter.hasNext()) {
                AutoOpInfo aoi = iter.next();

                at.safeAddClientAttr(ClientAttr.NICKNAME, aoi.name);
                at.safeAddClientAttr(ClientAttr.PROFILE, aoi.profile);
                at.safeAddClientAttr(ClientAttr.BAN_BANNER, aoi.oper);
            }
        }
        else
        {
            ServerLog.log("ADZ - autooplist is empty");
            at.safeAddClientAttr(ClientAttr.NICKNAME, invoker.getName());
            at.safeAddClientAttr(ClientAttr.PROFILE, "creator");
            at.safeAddClientAttr(ClientAttr.BAN_BANNER, invoker.getName());
        }

        ServerLog.log("ADZ - at=" + at);
        
        Command cmd = new Command(Command.BANLIST, at);
        invoker.getConnection().sendCommand(cmd);
    }

    public boolean isColosed() {
        return isClosed;
    }

    public boolean isFull() {
        return connectedUserRoles.size() >= slots;
    }

    public boolean isStatic() {
        return isStatic;
    }

    public void setStatic(boolean b) {
        isStatic = b;
        saveToSql();
    }

    public Iterator<User> getUserIterator() {
        return connectedUserRoles.keySet().iterator();
    }

    public int getUserCount() {
        int count = 0;
        Iterator<User> iter = connectedUserRoles.keySet().iterator();
        while (iter.hasNext()) {
            if (!iter.next().isHidden()) {
                count++;
            }
        }
        return count;
    }

    public boolean isImportant() {
        return isImportant;
    }

    public void setImportant(boolean b) {
        isImportant = b;
        saveToSql();
    }

    public String getNameId() {
        return nameId;
    }

    public String getName() {
        return name;
    }

    public Profile getProfile() {
        return profile;
    }

    public void setProfile(Profile newType) {
        profile = newType;
        saveToSql();
    }

    public String getTopic() {
        return topic;
    }

    public String getTopicBy() {
        return topicBy;
    }

    public String getCreatedBy() {

        if (isStatic()) {
            return "";
        }

        return createdBy;
    }

    public int getSlots() {
        return slots;
    }

    public void setSlots(int newSlots) {
        slots = newSlots;
        saveToSql();
    }

    public void kill() {

        close();

        if (sqlId == -1) {
            return;
        }

        mySql.killChannelData(sqlId);
        sqlId = -1;
    }

    public boolean kill(User invoker) {

        if (isStatic) {
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            Command cmd = new Command(Command.NOPERMISSION, at);
            invoker.getConnection().sendCommand(cmd);
            return false;
        }

        ChannelRole crole = getChannelRoleOfConnectedUser(invoker);

        if (!invoker.hasGlobalRight(Rights.Global.KillChannels)
                && !userHasChannelRight(invoker, crole, Rights.Channel.IsCreator)) {
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            Command cmd = new Command(Command.NOPERMISSION, at);
            invoker.getConnection().sendCommand(cmd);
            return false;
        }

        Date d = new Date();
        AttrList at = new AttrList();
        at.safeAddClientAttr(ClientAttr.NICKNAME, invoker.getName());
        at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
        at.safeAddClientAttr(ClientAttr.LOG_TIME, (int) (d.getTime() / 1000));
        Command cmd = new Command(Command.KILLEDCHANNEL, at);
        this.sendToConnectedUsers(cmd);

        close();

        if (sqlId == -1) {
            return true;
        }

        mySql.killChannelData(sqlId);
        sqlId = -1;

        return true;
    }

    public void close() {

        isClosed = true;

        server.onChannelClose(this);

        Iterator<User> iter = connectedUserRoles.keySet().iterator();
        while (iter.hasNext()) {
            User u = iter.next();

            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.NICKNAME, u.getName());
            at.safeAddClientAttr(ClientAttr.MEMBERCOUNT, 0);
            at.safeAddClientAttr(ClientAttr.CHATTERCOUNT, server.getChatterCount());
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            u.getConnection().sendCommand(new Command(Command.PARTED, at));
            u.getChannelList();
            u.onChannelClose(this);
        }
        connectedUserRoles.clear();
        connectedUserAutoRoles.clear();
        connectedUserDecidingRoles.clear();

    }

    public void sendToConnectedUsers(Command cmd) {
        for (User m : this.connectedUserRoles.keySet()) {
            try {
                m.getConnection().sendCommand(cmd);
            } catch (Exception ex) {
            }
        }
    }

    public void sendToConnectedUsers(Command cmd, User exclude) {
        for (User m : this.connectedUserRoles.keySet()) {
            try {
                if (!m.equals(exclude)) {
                    m.getConnection().sendCommand(cmd);
                }
            } catch (Exception ex) {
            }
        }
    }

    public void onUserDisconnect(User u) {
        if (connectedUserRoles.containsKey(u)) {
            part(u);
        }
    }

    public ChannelRole getChannelRoleOfConnectedUser(User u) {
        if (!connectedUserRoles.containsKey(u)) {
            return server.getConfig().getDefaultChannelRole();
        }

        return connectedUserRoles.get(u);
    }

    public ChannelRole getDecidingChannelRoleOfConnectedUser(User u) {
        if (!connectedUserDecidingRoles.containsKey(u)) {
            return server.getConfig().getDefaultChannelRole();
        }

        return connectedUserDecidingRoles.get(u);
    }

    private void setUserRole(User u, ChannelRole r, String roleBy, boolean auto, boolean write, boolean silent) {

        connectedUserRoles.put(u, r);

        if (auto) {
            boolean isDefaultChannelRole =
                    server.getConfig().getDefaultChannelRole().getSqlId() == r.getSqlId();

            if (isDefaultChannelRole) {
                connectedUserAutoRoles.remove(u);
            } else {
                connectedUserAutoRoles.put(u, r);
            }

            if (write) {
                if (isDefaultChannelRole) {
                    mySql.killChannelRoleOfUser(sqlId, u.getSqlId());
                } else {
                    mySql.updateChannelRoleOfUser(sqlId, u.getSqlId(), r.getSqlId(), roleBy);
                }

            }
        }

        ChannelRole autoRole = null;
        if (connectedUserAutoRoles.containsKey(u)) {
            autoRole = connectedUserAutoRoles.get(u);
        }

        if (autoRole != null && autoRole.hasRight(Rights.Channel.Op, r.getSqlId())) {
            connectedUserDecidingRoles.put(u, autoRole);
        } else {
            connectedUserDecidingRoles.put(u, r);
        }

        if (!silent) {
            Date d = new Date();
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.NICKNAME, roleBy);
            at.safeAddClientAttr(ClientAttr.TARGETNAME, u.getName());
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, name);
            at.safeAddClientAttr(ClientAttr.PROFILE, getUserChannelProfile(u));
            if (auto) {
                at.safeAddClientAttr(ClientAttr.PERSISTENT, 1);
            }
            at.safeAddClientAttr(ClientAttr.LOG_TIME, (int) (d.getTime() / 1000));
            Command cmd = new Command(Command.SETPROFILE, at);
            sendToConnectedUsers(cmd);
        }
    }

    public void updatedUserGlobalProfile(User u, String roleBy) {
        if (!connectedUserRoles.containsKey(u)) {
            return;
        }

        Date d = new Date();
        AttrList at = new AttrList();
        at.safeAddClientAttr(ClientAttr.NICKNAME, roleBy);
        at.safeAddClientAttr(ClientAttr.TARGETNAME, u.getName());
        at.safeAddClientAttr(ClientAttr.CHANNELNAME, name);
        at.safeAddClientAttr(ClientAttr.PROFILE, getUserChannelProfile(u));
        at.safeAddClientAttr(ClientAttr.LOG_TIME, (int) (d.getTime() / 1000));
        Command cmd = new Command(Command.SETPROFILE, at);
        sendToConnectedUsers(cmd);
    }

    public String getUserChannelProfile(User u) {

        if (!connectedUserRoles.containsKey(u)) {
            return "";
        }

        if (u.hasBirthday()) {
            return User.PROFILE_BIRTHDAY;
        }

        if (u.hasGlobalRight(Rights.Global.OverrideChannelProfile)) {
            return u.getDecidingGlobalRole().getProfile();
        }

        return connectedUserRoles.get(u).getProfile();
    }

    public boolean note(User invoker, String text, String format, String noteMe) {
        if (!connectedUserRoles.containsKey(invoker)) {
            return false;
        }

        ChannelRole crole = getChannelRoleOfConnectedUser(invoker);

        if (!userHasChannelRight(invoker, crole, Rights.Channel.Talk)) {
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            Command cmd = new Command(Command.NOPERMISSION, at);
            invoker.getConnection().sendCommand(cmd);
            return false;
        }

        if (profile == Profile.MODERATED) {
            if (!userHasChannelRight(invoker, crole, Rights.Channel.TalkOnModerated)) {
                AttrList at = new AttrList();
                at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
                Command cmd = new Command(Command.NOPERMISSION, at);
                invoker.getConnection().sendCommand(cmd);
                return false;
            }
        }

        AttrList at = new AttrList();
        at.safeAddClientAttr(ClientAttr.NICKNAME, invoker.getName());
        at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
        if (format != null && userHasChannelRight(invoker, crole, Rights.Channel.TalkWithColor)) {
            at.safeAddClientAttr(ClientAttr.NOTE_FORMAT, format);
        }
        if (noteMe != null) {
            at.safeAddClientAttr(ClientAttr.NOTE_ME, noteMe);
        }
        at.safeAddClientAttr(ClientAttr.NOTE_TEXT, text);

        Command cmd = new Command(Command.NOTE, at);
        this.sendToConnectedUsers(cmd);

        at.remove(ClientAttr.NOTE_FORMAT);
        addToEventLog(cmd.getByteCacheSafe(true));

        return true;
    }

    private ChannelRole getUserAutoChannelRole(User u) {
        ChannelRole role = null;

        if (!u.isGuest() && sqlId != -1) {
            int roleId = mySql.loadChannelRoleIdOfUser(sqlId, u.getSqlId());
            if (roleId != -1) {
                role = server.getConfig().findChannelRoleById(roleId);
            }
        }

        return role;
    }

    public boolean isAutoOpUser(User u) {
        return connectedUserAutoRoles.containsKey(u);
    }

    public boolean join(User invoker) {
        ServerLog.log("ADZ - Channel.java->join:invoker="+invoker);

        if (isFull()) {
            ServerLog.log("ADZ - Channel.java->join: channel is full");
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            Command cmd = new Command(Command.CHANNELFULL, at);
            invoker.getConnection().sendCommand(cmd);
            return false;
        }

        ChannelRole autoRole = getUserAutoChannelRole(invoker);
        ChannelRole role;

        if (autoRole != null) {
            role = autoRole;

            if (invoker.hasGlobalRight(Rights.Global.OverrideChannelProfile)) {
                forceChannelRoleProfileUsers.add(invoker);
            }
        } else {
            role = server.getConfig().getDefaultChannelRole();
        }


        if (!userHasChannelRight(invoker, role, Rights.Channel.IsCreator)
                && isBanned(invoker)) {
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            Command cmd = new Command(Command.BANNEDONCHANNEL, at);
            invoker.getConnection().sendCommand(cmd);
            return false;
        }

        if (profile
                == Profile.VIP) {
            if (!userHasChannelRight(invoker, role, Rights.Channel.IsVIP)) {
                AttrList at = new AttrList();
                at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
                Command cmd = new Command(Command.NOPERMISSION, at);
                invoker.getConnection().sendCommand(cmd);
                return false;
            }
        }

        if (autoRole
                != null) {
            setUserRole(invoker, role, "Server", true, false, true);
        } else {
            setUserRole(invoker, role, "Server", false, false, true);
        }
        Perms p = Perms.getChannelUserPerms(invoker, role);
        String up = getUserChannelProfile(invoker);
        AttrList at = new AttrList();

        at.safeAddClientAttr(ClientAttr.NICKNAME, invoker.getName());
        at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());



        try {
            at.add(p.toAttr(ClientAttr.PERMS));
        } catch (Exception ex) {
        }
        at.safeAddClientAttr(ClientAttr.PROFILE, up);

        at.safeAddClientAttr(ClientAttr.MEMBERCOUNT, getUserCount());
        at.safeAddClientAttr(ClientAttr.CHATTERCOUNT, server.getChatterCount());
        Command cmd = new Command(Command.JOINED, at);


        if (!invoker.isHidden()) {
            sendToConnectedUsers(cmd);
            //addToEventLog(cmd.getByteCacheSafe(true));
        } else {
            invoker.getConnection().sendCommand(cmd);
        }


        return true;
    }

    public boolean part(User invoker) {

        if (!connectedUserRoles.containsKey(invoker)) {
            return false;
        }

        AttrList at = new AttrList();
        at.safeAddClientAttr(ClientAttr.NICKNAME, invoker.getName());
        at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
        at.safeAddClientAttr(ClientAttr.MEMBERCOUNT, getUserCount() - 1);
        at.safeAddClientAttr(ClientAttr.CHATTERCOUNT, server.getChatterCount());
        Command cmd = new Command(Command.PARTED, at);

        if (!invoker.isHidden()) {
            sendToConnectedUsers(cmd);
            //addToEventLog(cmd.getByteCacheSafe(true));
        } else {
            invoker.getConnection().sendCommand(cmd);
        }

        connectedUserRoles.remove(invoker);
        connectedUserAutoRoles.remove(invoker);
        connectedUserDecidingRoles.remove(invoker);
        forceChannelRoleProfileUsers.remove(invoker);

        if (connectedUserRoles.size() < 1) {
            if (createdByGuest) {
                kill();
            } else if (!isStatic) {
                close();
            }
        }
        return true;
    }

    public void updateHideStatus(User u) {
        if (!connectedUserRoles.containsKey(u)) {
            return;
        }

        if (u.isHidden()) {
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.NICKNAME, u.getName());
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            at.safeAddClientAttr(ClientAttr.MEMBERCOUNT, getUserCount());
            at.safeAddClientAttr(ClientAttr.CHATTERCOUNT, server.getChatterCount());
            Command cmd = new Command(Command.PARTED, at);
            sendToConnectedUsers(cmd, u);
        } else {

            ChannelRole role = server.getConfig().getDefaultChannelRole();

            if (u.hasGlobalRight(Rights.Global.AdministrateChannels)) {
                role = server.getConfig().getDefaultCreatorChannelRole();
            } else if (!u.isGuest() && sqlId != -1) {
                int roleId = mySql.loadChannelRoleIdOfUser(sqlId, u.getSqlId());
                role = server.getConfig().findChannelRoleById(roleId);
            }


            Perms p = Perms.getChannelUserPerms(u, role);
            String up = getUserChannelProfile(u);

            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.NICKNAME, u.getName());
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            try {
                at.add(p.toAttr(ClientAttr.PERMS));
            } catch (Exception ex) {
            }
            at.safeAddClientAttr(ClientAttr.PROFILE, up);
            at.safeAddClientAttr(ClientAttr.MEMBERCOUNT, getUserCount());
            at.safeAddClientAttr(ClientAttr.CHATTERCOUNT, server.getChatterCount());
            Command cmd = new Command(Command.JOINED, at);
            sendToConnectedUsers(cmd, u);
        }
    }

    public boolean kick(User invoker, String kickUserName) {
        User target = findConnectedUser(kickUserName);

        if (target == null) {
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            at.safeAddClientAttr(ClientAttr.TARGETNAME, kickUserName);
            Command cmd = new Command(Command.NOTARGET, at);
            invoker.getConnection().sendCommand(cmd);
            return false;
        }
        return kick(invoker, target);
    }

    public boolean kick(User invoker, User target) {
        if (!connectedUserRoles.containsKey(invoker)) {
            return false;
        }
        if (!connectedUserRoles.containsKey(target)) {
            return false;
        }

        ChannelRole crole = getChannelRoleOfConnectedUser(invoker);
        ChannelRole targetCrole = getDecidingChannelRoleOfConnectedUser(target);

        Rights.Channel kickRight;
        if (isAutoOpUser(target)) {
            kickRight = Rights.Channel.Kick;
        } else {
            kickRight = Rights.Channel.KickAutoOp;
        }


        if (invoker == target || !userHasChannelRight(invoker, crole, target, targetCrole, kickRight)) {
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            Command cmd = new Command(Command.NOPERMISSION, at);
            invoker.getConnection().sendCommand(cmd);
            return false;
        }

        Date d = new Date();
        AttrList at = new AttrList();
        at.safeAddClientAttr(ClientAttr.NICKNAME, invoker.getName());
        at.safeAddClientAttr(ClientAttr.TARGETNAME, target.getName());
        at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
        at.safeAddClientAttr(ClientAttr.PROFILE, "banned");
        at.safeAddClientAttr(ClientAttr.LOG_TIME, (int) (d.getTime() / 1000));
        Command cmd = new Command(Command.SETPROFILE, at);
        this.sendToConnectedUsers(cmd);
        //addToEventLog(cmd.getByteCacheSafe(true));  

        at = new AttrList();
        at.safeAddClientAttr(ClientAttr.NICKNAME, target.getName());
        at.safeAddClientAttr(ClientAttr.MEMBERCOUNT, getUserCount() - 1);
        at.safeAddClientAttr(ClientAttr.CHATTERCOUNT, server.getChatterCount());
        at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
        sendToConnectedUsers(new Command(Command.PARTED, at));

        connectedUserRoles.remove(target);
        connectedUserAutoRoles.remove(target);
        connectedUserDecidingRoles.remove(target);
        forceChannelRoleProfileUsers.remove(invoker);
        target.onChannelClose(this);
        target.getChannelList();

        if (connectedUserRoles.size() < 1) {
            if (createdByGuest) {
                kill();
            } else if (!isStatic) {
                close();
            }
        }

        return true;
    }

    public boolean ban(User invoker, User target, int ipSize, long expires, String banner) {
        if (!connectedUserRoles.containsKey(invoker)) {
            return false;
        }

        ChannelRole crole = getChannelRoleOfConnectedUser(invoker);
        ChannelRole targetCrole = getDecidingChannelRoleOfConnectedUser(target);

        Rights.Channel banRight;
        if (isAutoOpUser(target)) {
            banRight = Rights.Channel.BanAutoOp;
        } else {
            banRight = Rights.Channel.Ban;
        }

        if (invoker == target || !userHasChannelRight(invoker, crole, target, targetCrole, banRight)) {
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            Command cmd = new Command(Command.NOPERMISSION, at);
            invoker.getConnection().sendCommand(cmd);
            return false;
        }

        if (isBanned(target)) {
            return true;
        }

        addBan(target.getConnection(), target.getName(), ipSize, expires, banner);

        Date d = new Date();
        AttrList at = new AttrList();
        at.safeAddClientAttr(ClientAttr.CHANNELNAME, name);
        at.safeAddClientAttr(ClientAttr.NICKNAME, invoker.getName());
        at.safeAddClientAttr(ClientAttr.TARGETNAME, target.getName());
        at.safeAddClientAttr(ClientAttr.PROFILE, "banned");
        at.safeAddClientAttr(ClientAttr.LOG_TIME, (int) (d.getTime() / 1000));
        Command cmd = new Command(Command.BANNED, at);
        sendToConnectedUsers(cmd);
        //addToEventLog(cmd.getByteCacheSafe(true));

        kick(invoker, target);

        return true;
    }

    public boolean unban(User invoker, String unBanUserName) {

        if (!connectedUserRoles.containsKey(invoker)) {
            return false;
        }

        ChannelRole crole = getChannelRoleOfConnectedUser(invoker);
        int targetCroleId = mySql.getChannelRoleIdOfUser(sqlId, unBanUserName);

        if (targetCroleId == -1) {
            if (!userHasChannelRightWithAnyModifier(invoker, crole, Rights.Channel.Ban)) {
                AttrList at = new AttrList();
                at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
                Command cmd = new Command(Command.NOPERMISSION, at);
                invoker.getConnection().sendCommand(cmd);
                return false;
            }
        } else {
            ChannelRole targetCRole = server.getConfig().findChannelRoleById(targetCroleId);
            if (!userHasChannelRight(invoker, crole, null, targetCRole, Rights.Channel.BanAutoOp)) {
                AttrList at = new AttrList();
                at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
                Command cmd = new Command(Command.NOPERMISSION, at);
                invoker.getConnection().sendCommand(cmd);
                return false;
            }
        }
        if (delBanByName(unBanUserName)) {

            Date d = new Date();
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, name);
            at.safeAddClientAttr(ClientAttr.NICKNAME, invoker.getName());
            at.safeAddClientAttr(ClientAttr.TARGETNAME, unBanUserName);
            at.safeAddClientAttr(ClientAttr.PROFILE, "banned");
            at.safeAddClientAttr(ClientAttr.LOG_TIME, (int) (d.getTime() / 1000));
            Command cmd = new Command(Command.UNBANNED, at);
            sendToConnectedUsers(cmd);
            //addToEventLog(cmd.getByteCacheSafe(true));
        }

        return true;
    }

    public boolean op(User invoker, String targetName, ChannelRole targetNewCrole, boolean auto) {
        if (!connectedUserRoles.containsKey(invoker)) {
            return false;
        }

        User target = findConnectedUser(targetName);

        if (target == null) {
            return false;
        }
        return op(invoker, target, targetNewCrole, auto);
    }

    public boolean op(User invoker, User target, ChannelRole targetNewCrole, boolean auto) {
        if (!connectedUserRoles.containsKey(invoker)) {
            return false;
        }

        if (!connectedUserRoles.containsKey(target)) {
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            at.safeAddClientAttr(ClientAttr.TARGETNAME, target.getName());
            Command cmd = new Command(Command.NOTARGET, at);
            invoker.getConnection().sendCommand(cmd);
            return false;
        }

        if (auto && target.isGuest()) {
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.EXEC, "Cannot autoop guests.");
            Command cmd = new Command(Command.FAILED, at);
            invoker.getConnection().sendCommand(cmd);
            return false;
        }

        if (auto && invoker == target
                && !invoker.hasGlobalRight(Rights.Global.AdministrateChannels)) {
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.EXEC, "Cannot autoop self.");
            Command cmd = new Command(Command.FAILED, at);
            invoker.getConnection().sendCommand(cmd);
            return false;
        }

        ChannelRole invokerCrole = getDecidingChannelRoleOfConnectedUser(invoker);
        ChannelRole targetCrole = getDecidingChannelRoleOfConnectedUser(target);

        // Does the invoker have the right to auto-op?
        if (auto) {
            if (!userHasChannelRight(invoker, invokerCrole, Rights.Channel.AutoOp)) {
                AttrList at = new AttrList();
                at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
                Command cmd = new Command(Command.NOPERMISSION, at);
                invoker.getConnection().sendCommand(cmd);
                ServerLog.log("op deny 1 (AutoOp)");
                return false;
            }

            if (!invoker.hasGlobalRight(Rights.Global.AdministrateChannels)
                    && target.hasGlobalRight(Rights.Global.AdministrateChannels)
                    && !forceChannelRoleProfileUsers.contains(target)) {
                AttrList at = new AttrList();
                at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
                Command cmd = new Command(Command.NOPERMISSION, at);
                invoker.getConnection().sendCommand(cmd);
                ServerLog.log("op deny 2 (forceChannelRoleProfileUsers)");
                return false;
            }

        }


        // Does the invoker have the right to op to this role?
        if (!userHasChannelRight(invoker, invokerCrole, target, targetNewCrole, Rights.Channel.OpTo)) {
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            Command cmd = new Command(Command.NOPERMISSION, at);
            invoker.getConnection().sendCommand(cmd);
            ServerLog.log("op deny 4 (OpTo)");
            return false;
        }


        Rights.Channel opRight;

        if (isAutoOpUser(target)) {
            opRight = Rights.Channel.OpAutoOp;
        } else {
            opRight = Rights.Channel.Op;
        }

        // Does the invoker have the right to op the target?
        if (!userHasChannelRight(invoker, invokerCrole, target, targetCrole, opRight)) {
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            Command cmd = new Command(Command.NOPERMISSION, at);
            invoker.getConnection().sendCommand(cmd);
            ServerLog.log("op deny 5 (Op)");
            return false;
        }

        // CanHaveChannelRights global/channel checks
        if (!target.hasGlobalRight(Rights.Global.CanHaveChannelRights)
                && userHasChannelRight(target, targetNewCrole, Rights.Channel.CanHaveChannelRights)) {

            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            Command cmd = new Command(Command.NOPERMISSION, at);
            invoker.getConnection().sendCommand(cmd);
            ServerLog.log("op deny 6 (CanHaveChannelRights)");
            return false;
        }

        if (!userHasChannelRight(target, targetCrole, Rights.Channel.CanHaveChannelRights)
                && userHasChannelRight(target, targetNewCrole, Rights.Channel.CanHaveChannelRights)
                && !userHasChannelRight(invoker, invokerCrole, Rights.Channel.IsCreator)) {

            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            Command cmd = new Command(Command.NOPERMISSION, at);
            invoker.getConnection().sendCommand(cmd);
            ServerLog.log("op deny 7 (CanHaveChannelRights)");
            return false;
        }

        ////// op valid after this line

        if (invoker == target
                && invoker.hasGlobalRight(Rights.Global.OverrideChannelProfile)) {
            forceChannelRoleProfileUsers.add(target);
        }

        Perms oldTargetPerms = getChannelUserPerms(target);

        if (auto && !target.isGuest() && sqlId != -1) {
            setUserRole(target, targetNewCrole, invoker.getName(), true, true, false);
        } else {
            setUserRole(target, targetNewCrole, invoker.getName(), false, true, false);
        }

        AttrList at = new AttrList();
        at.safeAddClientAttr(ClientAttr.CHANNELNAME, name);
        at.safeAddClientAttr(ClientAttr.NICKNAME, invoker.getName());
        at.safeAddClientAttr(ClientAttr.TARGETNAME, target.getName());
        try {
            at.add(oldTargetPerms.toAttr(ClientAttr.PERMS));
        } catch (Exception ex) {
        }
        Command cmd = new Command(Command.TOOKPERMS, at);
        sendToConnectedUsers(cmd);

        at = new AttrList();
        at.safeAddClientAttr(ClientAttr.CHANNELNAME, name);
        at.safeAddClientAttr(ClientAttr.NICKNAME, invoker.getName());
        at.safeAddClientAttr(ClientAttr.TARGETNAME, target.getName());
        try {
            Perms perms = getChannelUserPerms(target);
            at.add(perms.toAttr(ClientAttr.PERMS));
        } catch (Exception ex) {
        }
        cmd = new Command(Command.GAVEPERMS, at);
        sendToConnectedUsers(cmd);

        return true;
    }

    public boolean deop(User invoker, String targetName, boolean auto) {
        if (!connectedUserRoles.containsKey(invoker)) {
            return false;
        }

        ChannelRole targetNewCrole = server.getConfig().getDefaultChannelRole();
        ChannelRole targetCrole;
        int targetUserId;

        User target = findConnectedUser(targetName);

        if (target != null) {

            boolean ret = op(invoker, target, targetNewCrole, true);

            if (invoker == target
                    && invoker.hasGlobalRight(Rights.Global.OverrideChannelProfile)) {
                forceChannelRoleProfileUsers.remove(target);
            }

            return ret;

        } else {

            if (!auto) {
                AttrList at = new AttrList();
                at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
                at.safeAddClientAttr(ClientAttr.TARGETNAME, target.getName());
                Command cmd = new Command(Command.NOTARGET, at);
                invoker.getConnection().sendCommand(cmd);
                return false;
            } else {
                targetUserId = mySql.getUserIdOfUser(targetName);
                if (targetUserId == -1) {
                    AttrList at = new AttrList();
                    at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
                    at.safeAddClientAttr(ClientAttr.TARGETNAME, target.getName());
                    Command cmd = new Command(Command.NOTARGET, at);
                    invoker.getConnection().sendCommand(cmd);
                    return false;
                }
                int roleId = mySql.getChannelAutoOpRoleIdOfUser(sqlId, targetUserId);
                if (roleId == -1) {
                    AttrList at = new AttrList();
                    at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
                    at.safeAddClientAttr(ClientAttr.TARGETNAME, target.getName());
                    Command cmd = new Command(Command.NOTARGET, at);
                    invoker.getConnection().sendCommand(cmd);
                    return false;
                }
                targetCrole = server.getConfig().findChannelRoleById(roleId);

            }
        }

        ChannelRole invokerCrole = getDecidingChannelRoleOfConnectedUser(invoker);


        // Does the invoker have the right to auto-op?
        if (!userHasChannelRight(invoker, invokerCrole, Rights.Channel.AutoOp)) {
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            Command cmd = new Command(Command.NOPERMISSION, at);
            invoker.getConnection().sendCommand(cmd);
            ServerLog.log("op deny 1 (AutoOp)");
            return false;
        }

        // Does the invoker have the right to op to this role?
        if (!userHasChannelRight(invoker, invokerCrole, target, targetNewCrole, Rights.Channel.OpTo)) {
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            Command cmd = new Command(Command.NOPERMISSION, at);
            invoker.getConnection().sendCommand(cmd);
            ServerLog.log("op deny 3 (OpTo)");
            return false;
        }

        // Does the invoker have the right to op the target?
        if (!userHasChannelRight(invoker, invokerCrole, target, targetCrole, Rights.Channel.OpAutoOp)) {
            AttrList at = new AttrList();
            at.safeAddClientAttr(ClientAttr.CHANNELNAME, getName());
            Command cmd = new Command(Command.NOPERMISSION, at);
            invoker.getConnection().sendCommand(cmd);
            ServerLog.log("op deny 4 (Op)");
            return false;
        }

        ////// deop valid after this line

        mySql.killChannelRoleOfUser(sqlId, targetUserId);

        Date d = new Date();
        AttrList at = new AttrList();
        at.safeAddClientAttr(ClientAttr.NICKNAME, invoker.getName());
        at.safeAddClientAttr(ClientAttr.TARGETNAME, targetName);
        at.safeAddClientAttr(ClientAttr.CHANNELNAME, name);
        at.safeAddClientAttr(ClientAttr.PROFILE, targetNewCrole.getProfile());
        at.safeAddClientAttr(ClientAttr.PERSISTENT, 1);
        at.safeAddClientAttr(ClientAttr.LOG_TIME, (int) (d.getTime() / 1000));
        Command cmd = new Command(Command.SETPROFILE, at);
        sendToConnectedUsers(cmd);

        return true;
    }

    public User findConnectedUser(String name) {
        name = name.toLowerCase();
        Iterator<User> iter = connectedUserRoles.keySet().iterator();
        while (iter.hasNext()) {
            User u = iter.next();
            if (u.getLowerCaseName().equals(name)) {
                return u;
            }
        }
        return null;
    }

    public List<User> findUsers(String name, String profile, String[] userinfo) {
        return Server.findUsers(connectedUserRoles.keySet(), name, profile, userinfo);
    }

    public Perms getChannelUserPerms(String user) {
        User u = findConnectedUser(user);
        if (u == null) {
            return null;
        }

        return getChannelUserPerms(u);
    }

    public Perms getChannelUserPerms(User user) {

        ChannelRole r = getDecidingChannelRoleOfConnectedUser(user);
        return Perms.getChannelUserPerms(user, r);
    }

    public void setTopic(User invoker, String newTopic) {
        ChannelRole r;
        if (!connectedUserRoles.containsKey(invoker)) {
            if (invoker.isGuest() || sqlId == -1) {
                r = server.getConfig().getDefaultChannelRole();
            } else {
                int rId = mySql.loadChannelRoleIdOfUser(sqlId, invoker.getSqlId());
                r = server.getConfig().findChannelRoleById(rId);
            }
        } else {
            r = getDecidingChannelRoleOfConnectedUser(invoker);
        }

        if (!userHasChannelRight(invoker, r, Rights.Channel.SetTopic)) {
            Command cmd = new Command(Command.NOPERMISSION);
            invoker.getConnection().sendCommand(cmd);
            return;
        }

        topic = newTopic;
        topicBy = invoker.getName();

        saveToSql();

        AttrList at = new AttrList();
        getChannelInfo(at);
        Command cmd = new Command(Command.CHANNELINFO, at);
        sendToConnectedUsers(cmd);

    }

    public void resendNickList() {
        AttrList at = new AttrList();

        getChannelInfo(at);

        Iterator<User> iter = connectedUserRoles.keySet().iterator();
        while (iter.hasNext()) {
            User u = iter.next();
            if (u.isHidden()) {
                continue;
            }
            at.safeAddClientAttr(ClientAttr.NICKNAME, u.getName());

            try {
                at.add(getChannelUserPerms(u).toAttr(ClientAttr.PERMS));
            } catch (Exception ex) {
            }
            at.safeAddClientAttr(ClientAttr.PROFILE, getUserChannelProfile(u));
        }
        Command cmd = new Command(Command.NICKLIST, at);
        sendToConnectedUsers(cmd);
    }

    public void getChannelInfo(AttrList at) {

        at.safeAddClientAttr(ClientAttr.CHANNELNAME, name);
        if (!isStatic) {
            at.safeAddClientAttr(ClientAttr.CREATORNAME, createdBy);
        }
        at.safeAddClientAttr(ClientAttr.MEMBERCOUNT, getUserCount());
        at.safeAddClientAttr(ClientAttr.CHATTERCOUNT, server.getChatterCount());

        at.safeAddClientAttr(ClientAttr.PROFILE, profile.getProfile());

        at.safeAddClientAttr(ClientAttr.CHANNELINFO, topic);
        at.safeAddClientAttr(ClientAttr.CHANNELINFO, bgImageChat);
        at.safeAddClientAttr(ClientAttr.CHANNELINFO, bgImageList);
        at.safeAddClientAttr(ClientAttr.CHANNELINFO, isStatic ? "1" : "0");
        at.safeAddClientAttr(ClientAttr.CHANNELINFO, isImportant ? "1" : "0");


    }
}
